
lib mes = <alpha/mesdialog.ssz>;
lib sdl = <alpha/sdlplugin.ssz>;
lib s = <string.ssz>;
lib m = <math.ssz>;
lib se = <alpha/sdlevent.ssz>;
lib al = <alert.ssz>;
lib sff = "sff.ssz";
lib chr = "char.ssz";
lib cfg = "config.ssz";
lib com = "common.ssz";
lib fnt = "font.ssz";
lib sha = "share.ssz";
lib tbl = <table.ssz>;
lib file = <file.ssz>;
lib cmd = "command.ssz";
lib dscri = "debug-script.ssz";
lib tscri = "trigger-script.ssz";
lib snd = "sound.ssz";

const double lvmul = 2.0**(1.0/12.0);

&WincntMgr
{
  /^/char wincFN = "autolevel.txt";
  &.tbl.NameTable!int? winct;
  ^int zeroAry(int sz)
  {
    ^int a.new(sz);
    a:<-[void(i=){i = 0;}];
    ret a;
  }
  public void init()
  {
    if(!.com.autolevel) ret;
    ^char buf;
    buf = .s.u8ToS()<, .file.readAll!ubyte?(`wincFN);
    if(#buf > 0 && buf[0] == '\xfeff') buf = buf[1..-1];
    ^^/char lines = .s.splitLines(buf);
    lines:<-[void(l){
      ^^/char tmp = .s.split(",", l);
      if(#tmp < 2) ret;
      ^int item = .s.svToAry!int?(" ", .s.trim(tmp[1]));
      item = item[0 .. .sff.NumCharPalletes];
      if(#item < .sff.NumCharPalletes){
        item = item + `zeroAry(.sff.NumCharPalletes-#item);
      }
      `winct.set(tmp[0], item);
    }];
  }
  delete()
  {
    if(!.com.autolevel || !.com.matchOver()) ret;
    loop{index i = 0; while; do:
      if(#.chr.chars[i] > 0) branch{
      cond .chr.chars[i][0]~isWin():
        `win(i);
      cond .chr.chars[i][0]~isLose():
        `lose(i);
      }
      i++;
    while i < #.chr.chars:}
    %char buf;
    buf .= '\xfeff';
    `winct.forEach()<, [void(^/char name, ^int cnt){
      buf .= name .= ',';
      loop{index i = 0; while; do:
        buf .= ' ' .= ''cnt[i];
        i++;
      while i < .sff.NumCharPalletes:}
      buf .= \r\n;
    }];
    &.file.File f;
    f.open(`wincFN, "wb");
    f.writeAry!ubyte?(.s.sToU8(buf));
  }
  ^int getItem(^/char name)
  {
    ^int item = `winct.get(name);
    if(#item < .sff.NumCharPalletes){
      item = item + `zeroAry(.sff.NumCharPalletes-#item);
    }
    ret item;
  }
  void setItem(int pn, ^int item)
  {
    int ave = 0;
    loop{
      index i = 0;
      int palcnt = 0;
      while;
    do:
      if(.chr.cgi[pn].palSelectable[i]){
        ave += item[i];
        palcnt++;
      }
      i++;
    while i < #item:
      ave /= palcnt;
    }
    loop{index i = 0; while; do:
      if(!.chr.cgi[pn].palSelectable[i]) item[i] = ave;
      i++;
    while i < #item:}
    `winct.set(.chr.cgi[pn].def, item);
  }
  /int winPoint(index i)
  {
    ret
      .com.tmode[i+1&1] == (int).com.TeamMode::Simul ? (
        .com.tmode[i&1] != (int).com.TeamMode::Simul ? .com.numSimul[i+1&1]
        : .com.numSimul[i+1&1] > .com.numSimul[i&1]
        ? .com.numSimul[i+1&1] / .com.numSimul[i&1] : 1)
      : 1;
  }
  void win(index i)
  {
    ^int item = `getItem(.chr.cgi[i].def);
    item[.chr.chars[i][0]~palno()-1] += `winPoint(i);
    `setItem(i, item);
  }
  void lose(index i)
  {
    ^int item = `getItem(.chr.cgi[i].def);
    item[.chr.chars[i][0]~palno()-1] -= `winPoint(i);
    `setItem(i, item);
  }
  public int getLevel(index i)
  {
    ^int item = `getItem(.chr.cgi[i].def);
    ret item[.chr.cgi[i].palno-1];
  }
}
&WincntMgr wm;

void game()
{
  void put(float y=, ^/char txt)
  {
    bool tmp = .sff.allPalFX~enable;
    .sff.allPalFX~enable = false;
    loop{
      ^/char t = txt;
      index i;
    do:
      i = 0;
      loop{int w = 0; while; do:
        w += .fnt.debugFont~charWidth(t[i]) + .fnt.debugFont~spacingx;
        if(w > .cfg.Width) break;
        i++;
      while i < #t:}
      if(i == 0) i = 1;
      .fnt.debugFont~drawText(
        -(float)(.com.GameWidth-320)/2.0,
        y += (float).fnt.debugFont~sizey/.com.HeightScale,
        1.0/.com.WidthScale,  1.0/.com.HeightScale, 0, 1, t[0..i]);
      t = t[i..-1];
    while #t > 0:}
    .sff.allPalFX~enable = tmp;
  }
  %char line;
  void drawDebug()
  {:<-
    if(!.com.debugdraw || #.fnt.debugFont == 0) ret;
    float y = -(float)(.com.GameHeight-240);
    if(#.com.debugScript > 0) loop{index i = 0; while; do:
      if(#.chr.chars[i] > 0){
        .dscri.L.getGlobal("status");
        .dscri.L.pushNumber((double)(i+1));
        if(!.dscri.L.pcall(1, 1)) break;
        ^/char status;
        if(.dscri.L.isString(-1)) status = .dscri.L.toString(-1);
        .dscri.L.pop(1);
        if(#status > 0) `put(y=, status);
      }
      i++;
    while i < #.chr.chars:}
    y = .m.max!float?(y, 48.0 - (float)(.com.GameHeight-240));
    loop{index i = 0; while; do:
      if(#.chr.chars[i] > 0) `put(y=, .chr.cgi[i].def);
      i++;
    while i < #.chr.chars:}
    `put(y=, .chr.stg~def);
    `put(
      y=, (
        #.tscri.cwc > 0
        ? "P"+''(.tscri.cwc~playerno+1)+":" + .tscri.cwc~name : "")
      + "> " + `line);
    loop{index i = 0; while; do:
      if(#.chr.chars[i] > 0) loop{index j = 0; while; do:
        `put(y=, .com.clipboardText[i][j]);
        j++;
      while j < #.com.clipboardText[i]:}
      y += (float).fnt.debugFont~sizey/.com.HeightScale;
      i++;
    while i < #.chr.chars:}
  }
  void debugInput()
  {
    if(!.com.debugdraw || #.fnt.debugFont == 0) ret;
    char lastChar = .sdl.getLastChar();
    switch(lastChar){
    case '\0':
    case '\r':
      if(#.com.debugScript > 0){
        if(!.dscri.L.runString(`line)) .al.alert!self?(.dscri.L.toString(-1));
      }
      `line.new(0);
    case '\x8':
      {
        ^char tmp = `line[0..#`line-1];
        `line.new(0);
        `line .= tmp;
      }
    default:
      if(lastChar >= '\x20' && lastChar < '\x7f') `line .= lastChar;
    }
  }
  float x = 0.0, y = 0.0, l = 0.0, r = 0.0, bl = 0.0, br = 0.0;
  float scl = 1.0, sclmul = 1.0;
  float newx = x, newy = y;
  int oldp1wins, oldp2wins, olddraws;
  ^int lif.new(#.chr.chars), pow.new(#.chr.chars);
  ^^/int var.new(#.chr.chars);
  ^^/float fvar.new(#.chr.chars);
  void copyVar(int pno)
  {
    `lif[pno] = .chr.chars[pno][0]~sysivar[.chr.iLIFE];
    `pow[pno] = .chr.chars[pno][0]~sysivar[.chr.iPOWER];
    `var[pno] = .s.clone!int?(.chr.chars[pno][0]~ivar);
    `fvar[pno] = .s.clone!float?(.chr.chars[pno][0]~fvar);
  }
  void reset()
  {
    .com.p1wins = `oldp1wins;
    .com.p2wins = `oldp2wins;
    .com.draws = `olddraws;
    loop{index i = 0; do:
      if(#.chr.chars[i] > 0){
        .chr.chars[i][0]~sysivar[.chr.iLIFE] = `lif[i];
        .chr.chars[i][0]~sysivar[.chr.iPOWER] = `pow[i];
        .s.copy!int?(.chr.chars[i][0]~ivar, `var[i]);
        .s.copy!float?(.chr.chars[i][0]~fvar, `fvar[i]);
      }
      i++;
    while i < #.chr.chars:}
    .com.resetFrameTime();
    .chr.nextRound();
    `x = `newx = 0.0;
    `y = `newy = 0.0;
    `l = `r = 0.0;
    `scl = `sclmul = 1.0;
    .com.cam.update!.chr.stg<>.self?(`scl, `x, `y, .chr.stg<>=);
  }
  loop{
    int stagetime;
    ^&.sha.Share share;
    &.com.SuperDangerousRef oldshare;
    oldshare.copyRef!&.sha.Share?(share=);
    ^char ss;
    .mes.GetSharedString(:ss=:);
    ^^/char sss = .s.split("<>", ss);
    &.com.SuperDangerousRef sdr;
    sdr.pos = 0;
    sdr.l = typesize(&.sha.Share);
    sdr.p = .s.sToN!index?(sss[1]);
    sdr.copyToRef!&.sha.Share?(share=);
    share~push();
    if(#.com.debugScript > 0){
      ^/char err = .dscri.loadFile(.com.debugScript);
      if(#err > 0) .al.alert!.self?(err);
    }
    stagetime = share~stt;
    .cmd.synchronize();
    .wm.init();
    ^int level.new(#.chr.chars);
    loop{index i = 0; while; do:
      level[i] = 0;
      if(#.chr.chars[i] > 0){
        .chr.chars[i]<>~rootInit();
        level[i] = .wm.getLevel(i);
        if(.com.powerShare[i&1]){
          int pmax =
            .m.max!int?(
              .chr.chars[i&1]<>~sysivar[.chr.iPOWERMAX],
              .chr.chars[i]<>~sysivar[.chr.iPOWERMAX]);
          loop{index j = i&1; while; do:
            .chr.chars[j]<>~sysivar[.chr.iPOWERMAX] = pmax;
            j += 2;
          while j <= i:}
        }
      }
      i++;
    while i < #.chr.chars:}
    int minlv = level[0], maxlv = level[0];
    loop{index i = 1; while; do:
      if(#.chr.chars[i] > 0){
        minlv = .m.min!int?(minlv, level[i]);
        maxlv = .m.max!int?(maxlv, level[i]);
      }
      i++;
    while i < #.chr.chars:}
    branch{
    cond minlv > 0:
      loop{index i = 0; while; do:
        level[i] -= minlv;
        i++;
      while i < #.chr.chars:}
    cond maxlv < 0:
      loop{index i = 0; while; do:
        level[i] -= maxlv;
        i++;
      while i < #.chr.chars:}
    }
    loop{index i = 0; do:
      if(#.chr.chars[i] == 0) continue;
      .chr.players.add(.chr.chars[i][0]);
      {
        float lm = (float).chr.chars[i][0]~sysivar[.chr.iLIFEMAX];
        lm *= .com.life;
        switch(.com.tmode[i&1]){
        case (int).com.TeamMode::Single:
          switch(.com.tmode[i+1&1]){
          case (int).com.TeamMode::Simul:
            lm *= .com.team1VS2Life;
          case (int).com.TeamMode::Turns:
            if(.com.numturns[i+1&1] < .chr.fight~ro.match_wins && .com.sharedLife){
              lm =
                lm*(float).com.numturns[i+1&1]
                / (float).chr.fight~ro.match_wins;
            }
          }
        case (int).com.TeamMode::Simul:
          if(.com.sharedLife){
            switch(.com.tmode[i+1&1]){
            case (int).com.TeamMode::Simul:
              if(.com.numSimul[i+1&1] < .com.numSimul[i&1]){
                lm = lm*(float).com.numSimul[i+1&1] / (float).com.numSimul[i&1];
              }
            case (int).com.TeamMode::Turns:
              if(
                .com.numturns[i+1&1]
                < .com.numSimul[i&1] * .chr.fight~ro.match_wins)
              {
                lm =
                  lm*(float).com.numturns[i+1&1]
                  / (float)(.com.numSimul[i&1] * .chr.fight~ro.match_wins);
              }
            default:
              lm /= (float).com.numSimul[i&1];
            }
          }
        case (int).com.TeamMode::Turns:
          switch(.com.tmode[i+1&1]){
          case (int).com.TeamMode::Single:
            if(.chr.fight~ro.match_wins < .com.numturns[i&1] && .com.sharedLife){
              lm =
                lm * (float).chr.fight~ro.match_wins
                / (float).com.numturns[i&1];
            }
          case (int).com.TeamMode::Simul:
            if(
              .com.numSimul[i+1&1] * .chr.fight~ro.match_wins
              < .com.numturns[i&1])
            {
              lm =
                lm * .com.team1VS2Life
                * (float)(.com.numSimul[i+1&1] * .chr.fight~ro.match_wins)
                / (float).com.numturns[i&1];
            }
          case (int).com.TeamMode::Turns:
            if(.com.numturns[i+1&1] < .com.numturns[i&1] && .com.sharedLife){
              lm = lm*(float).com.numturns[i+1&1] / (float).com.numturns[i&1];
            }
          }
        }
        double hoge = .lvmul**((double)-level[i]);
        .chr.chars[i][0]~sysivar[.chr.iLIFEMAX] =
          .m.max!int?(1, (int).m.floor(hoge * lm));
        if(.com.rexisted[i&1] > 0){
          .chr.chars[i][0]~sysivar[.chr.iLIFE] =
            .m.min!int?(.chr.chars[i][0]~sysivar[.chr.iLIFEMAX])<, 
            (int).m.ceil(hoge * (float).chr.chars[i][0]~sysivar[.chr.iLIFE]);
        }
      }
      if(
        .com.round == 1 || (
          .com.tmode[i&1] == (int).com.TeamMode::Turns
          && .com.rexisted[i&1] == 0))
      {
        .chr.chars[i][0]~sysivar[.chr.iLIFE] =
          .chr.chars[i][0]~sysivar[.chr.iLIFEMAX];
        if(.com.round == 1) .chr.chars[i][0]~sysivar[.chr.iPOWER] = 0;
      }
      copyVar(i);
    continue:
      i++;
    while i < #.chr.chars:}
    .chr.stg~reset();//bgctrlの都合(循環参照可能な型を操作する)でここでreset
    loop{int i = 0; while; do:
      .chr.stg~action();
      i++;
    while i < stagetime:}
    //if(.com.round == 1) .snd.bgm~play(.chr.stg~bgmusic);
    .com.cam.init();
    .com.screenleft = (float).chr.stg~screenleft * .chr.stg~localscl;
    .com.screenright = (float).chr.stg~screenright * .chr.stg~localscl;
    float tmp;
    oldp1wins = .com.p1wins;
    oldp2wins = .com.p2wins;
    olddraws = .com.draws;
    reset();
  do:
    .com.step = false;
    .dscri.roundResetFlg = false;
    .dscri.reloadFlg = false;
    loop{index i = 0; while; do:
      if(.se.eventKeys[i].down){
        ^^/char scri = .com.hotkeys.get(.com.eventKeyHash(.se.eventKeys[i]));
        if(#scri > 0 && !.dscri.L.runString(scri<>)){
          .al.alert!self?(.dscri.L.toString(-1));
        }
      }
      i++;
    while i < #.se.eventKeys:}
    if(.dscri.roundResetFlg) reset();
    if(.dscri.reloadFlg){
      .mes.SetSharedString(:"reload":);
      ret;
    }
    if(.chr.roundOver()) branch{
      .com.round++;
      loop{index i = 0; while; do:
        .com.rexisted[i]++;
        i++;
      while i < #.com.rexisted:}
    cond
      !.com.matchOver()
      && (
        .com.tmode[0] != (int).com.TeamMode::Turns
        || .chr.chars[0][0]~isWin())
      && (
        .com.tmode[1] != (int).com.TeamMode::Turns
        || .chr.chars[1][0]~isWin())
      :
      loop{index i = 0; while; do:
        if(#.chr.chars[i] > 0){
          branch{
          cond
            .com.tmode[i&1] != (int).com.TeamMode::Turns
            || !.chr.chars[i]<>~isWin()
            :
            .chr.chars[i]<>~sysivar[.chr.iLIFE] =
              .chr.chars[i]<>~sysivar[.chr.iLIFEMAX];
          else:
            .m.limMin!int?(.chr.chars[i]<>~sysivar[.chr.iLIFE]=, 1);
          }
          copyVar(i);
        }
        i++;
      while i < #.chr.chars:}
      oldp1wins = .com.p1wins;
      oldp2wins = .com.p2wins;
      olddraws = .com.draws;
      reset();
    else:
      loop{index i = 0; while; do:
        branch{
        cond
          .chr.chars[i][0]~isWin() || (
            !.chr.chars[i][0]~isLose()
            && .com.tmode[i] != (int).com.TeamMode::Turns)
          :
          loop{index j = i; while; do:
            if(#.chr.chars[j] > 0){
              .chr.chars[j][0]~sysivar[.chr.iLIFE] =
                .m.max!int?(1, .chr.chars[j][0]~isWin() ? (int).m.ceil(
                  .lvmul**(double)level[j] * (float)(
                    .chr.chars[j][0]~sysivar[.chr.iLIFE]))
                : .chr.chars[j][0]~sysivar[.chr.iLIFEMAX2]);
            }
            j += 2;
          while j < #.chr.chars:}
        else:
          .chr.chars[i][0]~sysivar[.chr.iLIFE] = 0;
        }
        i++;
      while i < #.com.tmode:}
      break, break;
    }
    if(.com.turbo < 1.0) sclmul **= .com.turbo;
    scl = .com.cam.scaleBound(scl * sclmul);
    tmp = ((float).com.GameWidth/2.0) / scl;
    if(#((l + r) - (newx - x)*2.0) >= tmp / 2.0){
      tmp =
        .m.max!float?(0.0)<, .m.min!float?(tmp)<,
        .m.max!float?((newx-x)-l, r-(newx-x));
    }
    x =
      .com.cam.xBound(scl)<,
      .m.min!float?(x + l + tmp)<, .m.max!float?(x + r - tmp)<, newx;
    if(!.com.cam.zoom) x = .m.ceil(x*4.0 - 0.5)/4.0;
    y = .com.cam.yBound(scl, newy);
    if(
      .com.tickFrame() && (.chr.super <= 0 || .chr.superpausebg == 0)
      && (.chr.pause <= 0 || .chr.pausebg == 0)) 
    {
      .chr.stg~action();
      stagetime++;
    }
    newx = x;
    newy = y;
    sclmul = .chr.action(newx=, newy=, l=, r=, scl);
    debugInput();
    if(!.com.addFrameTime(.com.turbo)){
      if(!.se.eventUpdate()) break, while;
      break, do;
    }
    if(.se.fskip) while;
    {
      float dx = x, dy = y, dscl = scl;
      if(
        !.m.isnan(.com.drawscale)
        && !.m.isnan(.com.zoomposx)&& !.m.isnan(.com.zoomposy))
      {
        dscl =
          .m.max!float?(
            .com.cam.minScale, .com.drawscale / .com.cam.baseScale());
        dx = .com.cam.xBound(dscl, x + .com.zoomposx * (dscl - scl) / dscl);
        dy = y + .com.zoomposy;
      }
      .chr.draw(dx, dy, dscl);
    }
    drawDebug();
  /?/*.cfg.OpenGL:
    .sdl.GlSwapBuffers(::);
  /*true:
    .sdl.flip();
  /*?*/
  while !.se.esc && .cmd.update():
    if(.se.esc) .mes.SetSharedString(:"esc":);
  break:
    .chr.stg~bgctl.clear();//bgctrlをでfightingで開放
    if(#.cmd.net > 0) .cmd.net~stop();
    share~copy();
    share~stt = stagetime;
    oldshare.copyToRef!&.sha.Share?(share=);
  }
}

public void main()
{
  .game();
  if(.se.end) .mes.SetSharedString(:"end":);
}

